var AppRegistry = {
	'namenode': Namenode,
	'datanode': Datanode,
	'hdfsclient': HdfsClient
};

var Socket = function (host, app, fn, port) {
	this.host = host;
	this.app = app;
	this.fn = fn;
	this.port = port;
};

Socket.prototype.connect = function (destIP, destPort) {
	this.dest = destIP;
	this.dport = destPort;
};

Socket.prototype.send = function (data, size) {
	if (!this.host.network) return;
	this.host.network.transmit(
			this.host.ip,
			this.port,
			this.dest,
			this.dport,
			size,
			data);
};

Socket.prototype.sendto = function (destIP, destPort, data, size) {
	if (!this.host.network) return;
	this.host.network.transmit(
			this.host.ip,
			this.port,
			destIP,
			destPort,
			size,
			data);
};

Socket.prototype.recv = function (src, sport, dst, dport, data, size) {
	if (!this.fn) return;
	this.fn.call(this.app, src, sport, dport, data, size);
};

/**********************************************/

var Host = {
	start: function () {
		this.freePort = 1024;
		this.apps = {};
	},
	
	addApplication: function (appname) {
		if (!AppRegistry[appname]) {
			throw new Error("Cannot start application " + appname
					+ " at host " + this.name
					+ ". Application does not exist.");
		}
		
		if (!AppRegistry[appname]) return null;
		this.apps[appname] = this.sim.addEntity(AppRegistry[appname], this);
		return this.apps[appname];
	},
	
	killApplicaiton: function (appname) {
		var app = this.apps[appname];
		if (app && app.shutdown) app.shutdown();
		this.apps[appname] = null;
	},
	
	/** RPC related methods */
	registerService: function (app, serviceId) {
		// Does not check that network is connected
		// Does not check if a previous service was registered by this id
		// previous service by this id is 'overwritten'
		ARG_CHECK(arguments, 2, 2);
		if (!this.serviceList) this.serviceList = {};
		this.serviceList[serviceId] = app;
	},
	
	rpc: function (serviceIP, serviceId, func, data) {
		ARG_CHECK(arguments, 3, 4);
		if (!this.network) throw Constants.RPC.NETWORK_UNREACHABLE;
		var host = this.network.resolve(serviceIP);
		if (!host) throw Constants.RPC.HOST_UNREACHABLE;
		
		if (!host.serviceList[serviceId]) throw Constants.RPC.SERVICE_UNREACHABLE;
		var app = host.serviceList[serviceId];
		
		if (!func) throw Constants.RPC.METHOD_UNREACHABLE;
		
		if (data && data instanceof Array) {
			return func.apply(app, data);
		} else {
			return func.call(app, data);
		}
	},
	
	/** Socket related methods */
	socket: function (app, fn, listenport) {
		ARG_CHECK(arguments, 2, 3);
		if (!listenport) listenport = this.freePort ++;
		if (!this.sockets) this.sockets = {};
		if (this.sockets[listenport]) return null;
		
		var socket = new Socket(this, app, fn, listenport);
		this.sockets[listenport] = socket;
		return socket;
	},
	
	closesocket: function (port) {
		ARG_CHECK(arguments, 1, 1);
		if (!this.sockets) return;
		delete this.sockets[port];
	},
	
	recv: function (src, sport, dst, dport, data, size) {
		if (this.disconnected) return;
		if (!this.sockets) return;
		if (!this.sockets[dport]) return;
		
		this.sockets[dport].recv(src, sport, dst, dport, data, size);
	},
	
	disconnect: function () {
		this.disconnected = true;
		this.network = null;
	}
};
